/*
==========
Cariddi
==========
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program.  If not, see http://www.gnu.org/licenses/.

	@Repository:  https://github.com/edoardottt/cariddi

	@Author:      edoardottt, https://www.edoardottt.com

	@License: https://github.com/edoardottt/cariddi/blob/main/LICENSE

*/

package scanner

import (
	"regexp"
	"sync"
)

// Error struct.
// ErrorName = the name that identifies the error.
// Regex = The regular expression to be matched.
type Error struct {
	ErrorName string
	Regex     regexp.Regexp
}

// ErrorMatched struct.
// Error = Error struct.
// Url = url in which the error is found.
// Match = the string matching the regex.
type ErrorMatched struct {
	Error Error
	URL   string
	Match string
}

var (
	errors     []Error   //nolint: gochecknoglobals
	onceErrors sync.Once //nolint: gochecknoglobals
)

// GetErrorRegexes returns all the error structs.
func GetErrorRegexes() []Error {
	onceErrors.Do(func() {
		errors = []Error{
			{
				"PHP error",
				*regexp.MustCompile(`(?i)(php (warning|error)` +
					`|include_path` +
					`|undefined (variable|index)` +
					`|expect(s*) parameter [A-Za-z0-9-_]{1,30}` +
					`|call to undefined method` +
					`|failed to open stream` +
					`|cannot modify header information` +
					`|(syntax|parse|fatal) error` +
					`|safe mode restriction in effect` +
					`|uncaught exception)`),
			},
			{
				"ASP.Net error",
				*regexp.MustCompile(`(?i)(\"Message\"\:\"Invalid web service call` +
					`|\-\-\- End of inner exception stack trace \-\-\-` +
					`|Syntax error in string in query expression)`),
			},
			{
				"Python error",
				*regexp.MustCompile(`(?i)(Traceback \(most recent call last\)\:` +
					`|\(generated by waitress\)` +
					`|Syntax error in string in query expression)`),
			},
			{
				"General error",
				*regexp.MustCompile(`(?i)(fatal|critical|severe|high|medium|low) error`),
			},
			{
				"Debug information",
				*regexp.MustCompile(`(?i)(Debug trace|stack trace\:)`),
			},
			{
				"MySQL error",
				*regexp.MustCompile(`(?i)(valid MySQL result` +
					`|check the manual that (fits|corresponds to) your MySQL server version` +
					`|MySQLSyntaxErrorException` +
					`|MySqlException` +
					`|MySql error` +
					`|Unknown column ` +
					`|SQL syntax.*?MySQL` +
					`|Warning.*?mysqli?` +
					`|com\.mysql\.jdbc` +
					`|Zend_Db_(Adapter|Statement)_Mysqli_Exception` +
					`|Syntax error or access violation)`),
			},
			{
				"MariaDB error",
				*regexp.MustCompile(`(?i)(check the manual that (fits|corresponds to) your MariaDB server version` +
					`|MariaDB error)`),
			},
			{
				"PostgreSQL error",
				*regexp.MustCompile(`(?i)(valid PostgreSQL result` +
					`|PG::SyntaxError:` +
					`|PSQLException` +
					`|PostgreSQL query failed` +
					`|ERROR: parser: parse error at or near` +
					`|PostgreSQL error` +
					`|PostgreSQL.*?ERROR` +
					`|Warning.*?\\Wpg_` +
					`|Npgsql\.` +
					`|org\.postgresql\.util\.PSQLException` +
					`|ERROR:\s\ssyntax error at or near` +
					`|org\.postgresql\.jdbc)`),
			},
			{
				"MSSQL error",
				*regexp.MustCompile(`(?i)(Microsoft SQL error` +
					`|Microsoft SQL Native Client error` +
					`|ODBC SQL Server Driver` +
					`|Unclosed quotation mark after the character string` +
					`|SQLServer JDBC Driver` +
					`|Driver.*? SQL[\-\_\ ]*Server` +
					`|OLE DB.*? SQL Server` +
					`|\bSQL Server[^&lt;&quot;]+Driver|Warning.*?\W(mssql|sqlsrv)_` +
					`|\bSQL Server[^&lt;&quot;]+[0-9a-fA-F]{8}` +
					`|System\.Data\.SqlClient\.SqlException` +
					`|Exception.*?\bRoadhouse\.Cms\.` +
					`|\[SQL Server\]` +
					`|ODBC Driver \d+ for SQL Server` +
					`|com\.jnetdirect\.jsql` +
					`|macromedia\.jdbc\.sqlserver` +
					`|Zend_Db_(Adapter|Statement)_Sqlsrv_Exception` +
					`|com\.microsoft\.sqlserver\.jdbc` +
					`|SQL(Srv|Server)Exception` +
					`|Cannot initialize the data source object of OLE DB provider)`),
			},
			{
				"OracleDB error",
				*regexp.MustCompile(`(?i)(\bORA-\d{5}|Oracle error|Oracle.*Driver|Warning.*\Woci_.*|Warning.*\Wora_.*)`),
			},
			{
				"IBMDB2 error",
				*regexp.MustCompile(`(?i)(CLI Driver.*DB2|DB2 SQL error|\bdb2_\w+\(|SQLSTATE.+SQLCODE)`),
			},
			{
				"SQLite error",
				*regexp.MustCompile(`(?i)(SQLite\/JDBCDriver` +
					`|SQLite.Exception` +
					`|System.Data.SQLite.SQLiteException` +
					`|Warning.*sqlite_.*` +
					`|Warning.*SQLite3::` +
					`|\[SQLITE_ERROR\])`),
			},
		}
	})

	return errors
}

// RemoveDuplicateErrors removes duplicates from Errors found.
func RemoveDuplicateErrors(input []ErrorMatched) []ErrorMatched {
	keys := make(map[string]bool)
	list := []ErrorMatched{}

	for _, entry := range input {
		if _, value := keys[entry.Match+entry.URL]; !value {
			keys[entry.Match+entry.URL] = true
			list = append(list, entry)
		}
	}

	return list
}
